Problems with I2C LCD data when using FreeRTOS

Hi there,

In short, I’ve got an ESP32 development board (NodeMCU) and it’s connected to an LCD1602 lcd displaymodule via an I2C module.

On this LCD I want to display the current time (using NTP), the current temperature and whether or not the esp has an internet connection.

It all works fine just throwing it in an arduino loop, minus the fact that the clock doesn’t display smoothly (doesn’t quite update regularly enough), which got me looking at FreeRTOS (also was interested to learn about it).

I’ve got a few tasks set up, including one which updates all of the updatable LCD data (temp, time, connection status). It seems that I can’t get the LCD to update without the I2C data getting “corrupted” (?), presumably by FreeRTOS (it all works fine in a regular arduino loop).

Here is what the error feed from the serial monitor looked like:

[I][esp32-hal-i2c.c:1130] i2cProcQueue(): Bus busy, reinit
[D][esp32-hal-i2c.c:1336] i2cProcQueue(): Busy Timeout start=0x7a46, end=0x7a78, =50, max=50 error=1
[E][esp32-hal-i2c.c:318] i2cDumpI2c(): i2c=0x3ffbece0
[I][esp32-hal-i2c.c:319] i2cDumpI2c(): dev=0x60013000 date=0x16042000
[I][esp32-hal-i2c.c:321] i2cDumpI2c(): lock=0x3ffcb1c4
[I][esp32-hal-i2c.c:323] i2cDumpI2c(): num=0
[I][esp32-hal-i2c.c:324] i2cDumpI2c(): mode=1
[I][esp32-hal-i2c.c:325] i2cDumpI2c(): stage=3
[I][esp32-hal-i2c.c:326] i2cDumpI2c(): error=1
[I][esp32-hal-i2c.c:327] i2cDumpI2c(): event=0x3ffc95cc bits=0
[I][esp32-hal-i2c.c:328] i2cDumpI2c(): intr_handle=0x3ffcb12c
[I][esp32-hal-i2c.c:329] i2cDumpI2c(): dq=0x3ffcb4c8
[I][esp32-hal-i2c.c:330] i2cDumpI2c(): queueCount=1
[I][esp32-hal-i2c.c:331] i2cDumpI2c(): queuePos=0
[I][esp32-hal-i2c.c:332] i2cDumpI2c(): errorByteCnt=0
[I][esp32-hal-i2c.c:333] i2cDumpI2c(): errorQueue=0
[I][esp32-hal-i2c.c:334] i2cDumpI2c(): debugFlags=0x00000000
[I][esp32-hal-i2c.c:311] i2cDumpDqData(): Debug Buffer not Enabled
[I][esp32-hal-i2c.c:354] i2cDumpInts(): Debug Buffer not Enabled
[I][esp32-hal-i2c.c:1130] i2cProcQueue(): Bus busy, reinit
[D][esp32-hal-i2c.c:1336] i2cProcQueue(): Busy Timeout start=0x7acd, end=0x7aff, =50, max=50 error=1
[E][esp32-hal-i2c.c:318] i2cDumpI2c(): i2c=0x3ffbece0
[I][esp32-hal-i2c.c:319] i2cDumpI2c(): dev=0x60013000 date=0x16042000
[I][esp32-hal-i2c.c:321] i2cDumpI2c(): lock=0x3ffcb1c4
[I][esp32-hal-i2c.c:323] i2cDumpI2c(): num=0
[I][esp32-hal-i2c.c:324] i2cDumpI2c(): mode=1
[I][esp32-hal-i2c.c:325] i2cDumpI2c(): stage=3
[I][esp32-hal-i2c.c:326] i2cDumpI2c(): error=1
[I][esp32-hal-i2c.c:327] i2cDumpI2c(): event=0x3ffc95cc bits=0
[I][esp32-hal-i2c.c:328] i2cDumpI2c(): intr_handle=0x3ffcb12c
[I][esp32-hal-i2c.c:329] i2cDumpI2c(): dq=0x3ffcb4c8
[I][esp32-hal-i2c.c:330] i2cDumpI2c(): queueCount=1
[I][esp32-hal-i2c.c:331] i2cDumpI2c(): queuePos=0
[I][esp32-hal-i2c.c:332] i2cDumpI2c(): errorByteCnt=0
[I][esp32-hal-i2c.c:333] i2cDumpI2c(): errorQueue=0
[I][esp32-hal-i2c.c:334] i2cDumpI2c(): debugFlags=0x00000000
[I][esp32-hal-i2c.c:311] i2cDumpDqData(): Debug Buffer not Enabled
[I][esp32-hal-i2c.c:354] i2cDumpInts(): Debug Buffer not Enabled

However now it’s changed and looks like this - I don’t know what I changed.

ets Jun 8 2016 00:22:57

rst:0xc (SW_CPU_RESET),boot:0x12 (SPI_FAST_FLASH_BOOT)
configsip: 0, SPIWP:0xee
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
mode:DIO, clock div:1
load:0x3fff0018,len:4
load:0x3fff001c,len:928
ho 0 tail 12 room 4
load:0x40078000,len:8424
ho 0 tail 12 room 4
load:0x40080400,len:5868
entry 0x4008069c
Booting
[D][WiFiGeneric.cpp:336] _eventCallback(): Event: 0 - WIFI_READY
[D][WiFiGeneric.cpp:336] _eventCallback(): Event: 2 - STA_START
[D][WiFiGeneric.cpp:336] _eventCallback(): Event: 4 - STA_CONNECTED
[D][WiFiGeneric.cpp:336] _eventCallback(): Event: 7 - STA_GOT_IP
[D][WiFiGeneric.cpp:379] _eventCallback(): STA IP: 192.168.0.23, MASK: 255.255.255.0, GW: 192.168.0.1
Ready
IP address: 192.168.0.23
Guru Meditation Error: Core 0 panic’ed (IllegalInstruction). Exception was unhandled.
Memory dump at 0x400e805c: 3df03d00 3db03df0 0f0122f0
Core 0 register dump:
PC : 0x400e8061 PS : 0x00060b33 A0 : 0x800e807e A1 : 0x3ffbc080
A2 : 0x00000001 A3 : 0x00000001 A4 : 0x800891e8 A5 : 0x3ffb5100
A6 : 0x00000000 A7 : 0x3ffbbda0 A8 : 0x800e8046 A9 : 0x3ffbc060
A10 : 0x3ffbd378 A11 : 0x3ffbc08f A12 : 0x00000001 A13 : 0x00000001
A14 : 0x00060b20 A15 : 0x00000000 SAR : 0x00000000 EXCCAUSE: 0x00000000
EXCVADDR: 0x00000000 LBEG : 0x00000000 LEND : 0x00000000 LCOUNT : 0x00000000

Backtrace: 0x400e8061:0x3ffbc080 0x400e807b:0x3ffbc0b0 0x400e63b5:0x3ffbc0d0 0x4008aed5:0x3ffbc0f0 0x40088fd5:0x3ffbc110

Rebooting…

On the LCD display it just look like random characters changing on the screen every second or so.

My code is here (can’t upload direct to website sorry): ESP_time_temp_lcd_v3.ino - Google Drive

I understand the code is not very neat or well written, sorry about that. I’m also a beginner to RTOSs.

Any help would be appreciated,

Cheers,
Will

How is the I2C driver implemented? By which I mean, is it polling, or interrupt driver, or combination of the two, or something else?

Thanks for your reply Richard,

I’m not sure, I’m quite a beginner. I’m using the LiquidCrystal_I2C library by Frank de Brabander.

How would I find out the answer to your question?

Cheers,
Will

You would have to post the LCD driver code. The code you posted before doesn’t show it but does show you are not really using the kernel as intended. The tasks defined in that code just repeatedly call a function, delay for a fixed period, then call the function again. You are not getting much benefit from the kernel doing things that way. When using any multithreading system on a microcontroller it is best to try and make your system event driven, so the tasks perform an action in response to an event, not just of a fixed time bases. Making the system event driven will use a lot less CPU time, less power, etc.

Hi Richard,

That makes sense, here is the code for the LCD driver. It uses the Wire library to communicate with the LCD display, I’m guessing that might be where the problem lies, so I’ve posted that below the source code for the LCD library.

If I’m understanding what you’re saying correctly, I should be writing code which only updates the lcd display when the temperature changes or the clock updates, is that right? Thinking about that now, that seems logical.

Sorry about the long message

LCD Header file

#ifndef LiquidCrystal_I2C_h
#define LiquidCrystal_I2C_h

#include <inttypes.h>
#include “Print.h”
#include <Wire.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_ENTRYRIGHT 0x00
#define LCD_ENTRYLEFT 0x02
#define LCD_ENTRYSHIFTINCREMENT 0x01
#define LCD_ENTRYSHIFTDECREMENT 0x00

// flags for display on/off control
#define LCD_DISPLAYON 0x04
#define LCD_DISPLAYOFF 0x00
#define LCD_CURSORON 0x02
#define LCD_CURSOROFF 0x00
#define LCD_BLINKON 0x01
#define LCD_BLINKOFF 0x00

// flags for display/cursor shift
#define LCD_DISPLAYMOVE 0x08
#define LCD_CURSORMOVE 0x00
#define LCD_MOVERIGHT 0x04
#define LCD_MOVELEFT 0x00

// flags for function set
#define LCD_8BITMODE 0x10
#define LCD_4BITMODE 0x00
#define LCD_2LINE 0x08
#define LCD_1LINE 0x00
#define LCD_5x10DOTS 0x04
#define LCD_5x8DOTS 0x00

// flags for backlight control
#define LCD_BACKLIGHT 0x08
#define LCD_NOBACKLIGHT 0x00

#define En B00000100 // Enable bit
#define Rw B00000010 // Read/Write bit
#define Rs B00000001 // Register select bit

class LiquidCrystal_I2C : public Print {
public:
LiquidCrystal_I2C(uint8_t lcd_Addr,uint8_t lcd_cols,uint8_t lcd_rows);
void begin(uint8_t cols, uint8_t rows, uint8_t charsize = LCD_5x8DOTS );
void clear();
void home();
void noDisplay();
void display();
void noBlink();
void blink();
void noCursor();
void cursor();
void scrollDisplayLeft();
void scrollDisplayRight();
void printLeft();
void printRight();
void leftToRight();
void rightToLeft();
void shiftIncrement();
void shiftDecrement();
void noBacklight();
void backlight();
void autoscroll();
void noAutoscroll();
void createChar(uint8_t, uint8_t);
void createChar(uint8_t location, const char *charmap);
// Example: const char bell[8] PROGMEM = {B00100,B01110,B01110,B01110,B11111,B00000,B00100,B00000};

void setCursor(uint8_t, uint8_t);
#if defined(ARDUINO) && ARDUINO >= 100
virtual size_t write(uint8_t);
#else
virtual void write(uint8_t);
#endif
void command(uint8_t);
void init();
void oled_init();

////compatibility API function aliases
void blink_on(); // alias for blink()
void blink_off(); // alias for noBlink()
void cursor_on(); // alias for cursor()
void cursor_off(); // alias for noCursor()
void setBacklight(uint8_t new_val); // alias for backlight() and nobacklight()
void load_custom_character(uint8_t char_num, uint8_t *rows); // alias for createChar()
void printstr(const char);

////Unsupported API functions (not implemented in this library)
uint8_t status();
void setContrast(uint8_t new_val);
uint8_t keypad();
void setDelay(int,int);
void on();
void off();
uint8_t init_bargraph(uint8_t graphtype);
void draw_horizontal_graph(uint8_t row, uint8_t column, uint8_t len, uint8_t pixel_col_end);
void draw_vertical_graph(uint8_t row, uint8_t column, uint8_t len, uint8_t pixel_col_end);

private:
void init_priv();
void send(uint8_t, uint8_t);
void write4bits(uint8_t);
void expanderWrite(uint8_t);
void pulseEnable(uint8_t);
uint8_t _Addr;
uint8_t _displayfunction;
uint8_t _displaycontrol;
uint8_t _displaymode;
uint8_t _numlines;
bool _oled = false;
uint8_t _cols;
uint8_t _rows;
uint8_t _backlightval;
};

#endif

LCD .cpp file

#include “LiquidCrystal_I2C.h”
#include <inttypes.h>
#if defined(ARDUINO) && ARDUINO >= 100

#include “Arduino.h”

#define printIIC(args) Wire.write(args)
inline size_t LiquidCrystal_I2C::write(uint8_t value) {
send(value, Rs);
return 1;
}

#else
#include “WProgram.h”

#define printIIC(args) Wire.send(args)
inline void LiquidCrystal_I2C::write(uint8_t value) {
send(value, Rs);
}

#endif
#include “Wire.h”

// When the display powers up, it is configured as follows:
//
// 1. Display clear
// 2. Function set:
// DL = 1; 8-bit interface data
// N = 0; 1-line display
// F = 0; 5x8 dot character font
// 3. Display on/off control:
// D = 0; Display off
// C = 0; Cursor off
// B = 0; Blinking off
// 4. Entry mode set:
// I/D = 1; Increment by 1
// S = 0; No shift
//
// Note, however, that resetting the Arduino doesn’t reset the LCD, so we
// can’t assume that its in that state when a sketch starts (and the
// LiquidCrystal constructor is called).

LiquidCrystal_I2C::LiquidCrystal_I2C(uint8_t lcd_Addr,uint8_t lcd_cols,uint8_t lcd_rows)
{
_Addr = lcd_Addr;
_cols = lcd_cols;
_rows = lcd_rows;
_backlightval = LCD_NOBACKLIGHT;
}

void LiquidCrystal_I2C::oled_init(){
_oled = true;
init_priv();
}

void LiquidCrystal_I2C::init(){
init_priv();
}

void LiquidCrystal_I2C::init_priv()
{
Wire.begin();
_displayfunction = LCD_4BITMODE | LCD_1LINE | LCD_5x8DOTS;
begin(_cols, _rows);
}

void LiquidCrystal_I2C::begin(uint8_t cols, uint8_t lines, uint8_t dotsize) {
if (lines > 1) {
_displayfunction |= LCD_2LINE;
}
_numlines = lines;

// for some 1 line displays you can select a 10 pixel high font
if ((dotsize != 0) && (lines == 1)) {
_displayfunction |= LCD_5x10DOTS;
}

// SEE PAGE 45/46 FOR INITIALIZATION SPECIFICATION!
// according to datasheet, we need at least 40ms after power rises above 2.7V
// before sending commands. Arduino can turn on way befer 4.5V so we’ll wait 50
delay(50);

// Now we pull both RS and R/W low to begin commands
expanderWrite(_backlightval); // reset expanderand turn backlight off (Bit 8 =1)
delay(1000);

  //put the LCD into 4 bit mode

// this is according to the hitachi HD44780 datasheet
// figure 24, pg 46

// we start in 8bit mode, try to set 4 bit mode

write4bits(0x03 << 4);
delayMicroseconds(4500); // wait min 4.1ms

// second try
write4bits(0x03 << 4);
delayMicroseconds(4500); // wait min 4.1ms

// third go!
write4bits(0x03 << 4);
delayMicroseconds(150);

// finally, set to 4-bit interface
write4bits(0x02 << 4);

// set # lines, font size, etc.
command(LCD_FUNCTIONSET | _displayfunction);

// turn the display on with no cursor or blinking default
_displaycontrol = LCD_DISPLAYON | LCD_CURSOROFF | LCD_BLINKOFF;
display();

// clear it off
clear();

// Initialize to default text direction (for roman languages)
_displaymode = LCD_ENTRYLEFT | LCD_ENTRYSHIFTDECREMENT;

// set the entry mode
command(LCD_ENTRYMODESET | _displaymode);

home();

}

/********** high level commands, for the user! */
void LiquidCrystal_I2C::clear(){
command(LCD_CLEARDISPLAY);// clear display, set cursor position to zero
delayMicroseconds(2000); // this command takes a long time!
if (_oled) setCursor(0,0);
}

void LiquidCrystal_I2C::home(){
command(LCD_RETURNHOME); // set cursor position to zero
delayMicroseconds(2000); // this command takes a long time!
}

void LiquidCrystal_I2C::setCursor(uint8_t col, uint8_t row){
int row_offsets = { 0x00, 0x40, 0x14, 0x54 };
if ( row > _numlines ) {
row = _numlines-1; // we count rows starting w/0
}
command(LCD_SETDDRAMADDR | (col + row_offsets[row]));
}

// Turn the display on/off (quickly)
void LiquidCrystal_I2C::noDisplay() {
_displaycontrol &= ~LCD_DISPLAYON;
command(LCD_DISPLAYCONTROL | _displaycontrol);
}
void LiquidCrystal_I2C::display() {
_displaycontrol |= LCD_DISPLAYON;
command(LCD_DISPLAYCONTROL | _displaycontrol);
}

// Turns the underline cursor on/off
void LiquidCrystal_I2C::noCursor() {
_displaycontrol &= ~LCD_CURSORON;
command(LCD_DISPLAYCONTROL | _displaycontrol);
}
void LiquidCrystal_I2C::cursor() {
_displaycontrol |= LCD_CURSORON;
command(LCD_DISPLAYCONTROL | _displaycontrol);
}

// Turn on and off the blinking cursor
void LiquidCrystal_I2C::noBlink() {
_displaycontrol &= ~LCD_BLINKON;
command(LCD_DISPLAYCONTROL | _displaycontrol);
}
void LiquidCrystal_I2C::blink() {
_displaycontrol |= LCD_BLINKON;
command(LCD_DISPLAYCONTROL | _displaycontrol);
}

// These commands scroll the display without changing the RAM
void LiquidCrystal_I2C::scrollDisplayLeft(void) {
command(LCD_CURSORSHIFT | LCD_DISPLAYMOVE | LCD_MOVELEFT);
}
void LiquidCrystal_I2C::scrollDisplayRight(void) {
command(LCD_CURSORSHIFT | LCD_DISPLAYMOVE | LCD_MOVERIGHT);
}

// This is for text that flows Left to Right
void LiquidCrystal_I2C::leftToRight(void) {
_displaymode |= LCD_ENTRYLEFT;
command(LCD_ENTRYMODESET | _displaymode);
}

// This is for text that flows Right to Left
void LiquidCrystal_I2C::rightToLeft(void) {
_displaymode &= ~LCD_ENTRYLEFT;
command(LCD_ENTRYMODESET | _displaymode);
}

// This will ‘right justify’ text from the cursor
void LiquidCrystal_I2C::autoscroll(void) {
_displaymode |= LCD_ENTRYSHIFTINCREMENT;
command(LCD_ENTRYMODESET | _displaymode);
}

// This will ‘left justify’ text from the cursor
void LiquidCrystal_I2C::noAutoscroll(void) {
_displaymode &= ~LCD_ENTRYSHIFTINCREMENT;
command(LCD_ENTRYMODESET | _displaymode);
}

// Allows us to fill the first 8 CGRAM locations
// with custom characters
void LiquidCrystal_I2C::createChar(uint8_t location, uint8_t charmap) {
location &= 0x7; // we only have 8 locations 0-7
command(LCD_SETCGRAMADDR | (location << 3));
for (int i=0; i<8; i++) {
write(charmap[i]);
}
}

//createChar with PROGMEM input
void LiquidCrystal_I2C::createChar(uint8_t location, const char *charmap) {
location &= 0x7; // we only have 8 locations 0-7
command(LCD_SETCGRAMADDR | (location << 3));
for (int i=0; i<8; i++) {
write(pgm_read_byte_near(charmap++));
}
}

// Turn the (optional) backlight off/on
void LiquidCrystal_I2C::noBacklight(void) {
_backlightval=LCD_NOBACKLIGHT;
expanderWrite(0);
}

void LiquidCrystal_I2C::backlight(void) {
_backlightval=LCD_BACKLIGHT;
expanderWrite(0);
}

/*********** mid level commands, for sending data/cmds */

inline void LiquidCrystal_I2C::command(uint8_t value) {
send(value, 0);
}

/************ low level data pushing commands **********/

// write either command or data
void LiquidCrystal_I2C::send(uint8_t value, uint8_t mode) {
uint8_t highnib=value&0xf0;
uint8_t lownib=(value<<4)&0xf0;
write4bits((highnib)|mode);
write4bits((lownib)|mode);
}

void LiquidCrystal_I2C::write4bits(uint8_t value) {
expanderWrite(value);
pulseEnable(value);
}

void LiquidCrystal_I2C::expanderWrite(uint8_t _data){
Wire.beginTransmission(_Addr);
printIIC((int)(_data) | _backlightval);
Wire.endTransmission();
}

void LiquidCrystal_I2C::pulseEnable(uint8_t _data){
expanderWrite(_data | En); // En high
delayMicroseconds(1); // enable pulse must be >450ns

expanderWrite(_data & ~En); // En low
delayMicroseconds(50); // commands need > 37us to settle
}

// Alias functions

void LiquidCrystal_I2C::cursor_on(){
cursor();
}

void LiquidCrystal_I2C::cursor_off(){
noCursor();
}

void LiquidCrystal_I2C::blink_on(){
blink();
}

void LiquidCrystal_I2C::blink_off(){
noBlink();
}

void LiquidCrystal_I2C::load_custom_character(uint8_t char_num, uint8_t *rows){
createChar(char_num, rows);
}

void LiquidCrystal_I2C::setBacklight(uint8_t new_val){
if(new_val){
backlight(); // turn backlight on
}else{
noBacklight(); // turn backlight off
}
}

void LiquidCrystal_I2C::printstr(const char c){
//This function is not identical to the function used for “real” I2C displays
//it’s here so the user sketch doesn’t have to be changed
print(c);
}

// unsupported API functions
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored “-Wunused-parameter”
void LiquidCrystal_I2C::off(){}
void LiquidCrystal_I2C::on(){}
void LiquidCrystal_I2C::setDelay (int cmdDelay,int charDelay) {}
uint8_t LiquidCrystal_I2C::status(){return 0;}
uint8_t LiquidCrystal_I2C::keypad (){return 0;}
uint8_t LiquidCrystal_I2C::init_bargraph(uint8_t graphtype){return 0;}
void LiquidCrystal_I2C::draw_horizontal_graph(uint8_t row, uint8_t column, uint8_t len, uint8_t pixel_col_end){}
void LiquidCrystal_I2C::draw_vertical_graph(uint8_t row, uint8_t column, uint8_t len, uint8_t pixel_row_end){}
void LiquidCrystal_I2C::setContrast(uint8_t new_val){}
#pragma GCC diagnostic pop

Wire header

#ifndef TwoWire_h
#define TwoWire_h

#include <inttypes.h>
#include “Stream.h”

#define BUFFER_LENGTH 32

// WIRE_HAS_END means Wire has end()
#define WIRE_HAS_END 1

class TwoWire : public Stream
{
private:
static uint8_t rxBuffer;
static uint8_t rxBufferIndex;
static uint8_t rxBufferLength;

static uint8_t txAddress;
static uint8_t txBuffer[];
static uint8_t txBufferIndex;
static uint8_t txBufferLength;

static uint8_t transmitting;
static void (*user_onRequest)(void);
static void (*user_onReceive)(int);
static void onRequestService(void);
static void onReceiveService(uint8_t*, int);

public:
TwoWire();
void begin();
void begin(uint8_t);
void begin(int);
void end();
void setClock(uint32_t);
void beginTransmission(uint8_t);
void beginTransmission(int);
uint8_t endTransmission(void);
uint8_t endTransmission(uint8_t);
uint8_t requestFrom(uint8_t, uint8_t);
uint8_t requestFrom(uint8_t, uint8_t, uint8_t);
uint8_t requestFrom(uint8_t, uint8_t, uint32_t, uint8_t, uint8_t);
uint8_t requestFrom(int, int);
uint8_t requestFrom(int, int, int);
virtual size_t write(uint8_t);
virtual size_t write(const uint8_t , size_t);
virtual int available(void);
virtual int read(void);
virtual int peek(void);
virtual void flush(void);
void onReceive( void (
)(int) );
void onRequest( void (*)(void) );

inline size_t write(unsigned long n) { return write((uint8_t)n); }
inline size_t write(long n) { return write((uint8_t)n); }
inline size_t write(unsigned int n) { return write((uint8_t)n); }
inline size_t write(int n) { return write((uint8_t)n); }
using Print::write;

};

extern TwoWire Wire;

#endif

Wire .cpp

extern “C” {
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#include “utility/twi.h”
}

#include “Wire.h”

// Initialize Class Variables //////////////////////////////////////////////////

uint8_t TwoWire::rxBuffer[BUFFER_LENGTH];
uint8_t TwoWire::rxBufferIndex = 0;
uint8_t TwoWire::rxBufferLength = 0;

uint8_t TwoWire::txAddress = 0;
uint8_t TwoWire::txBuffer[BUFFER_LENGTH];
uint8_t TwoWire::txBufferIndex = 0;
uint8_t TwoWire::txBufferLength = 0;

uint8_t TwoWire::transmitting = 0;
void (*TwoWire::user_onRequest)(void);
void (*TwoWire::user_onReceive)(int);

// Constructors ////////////////////////////////////////////////////////////////

TwoWire::TwoWire()
{
}

// Public Methods //////////////////////////////////////////////////////////////

void TwoWire::begin(void)
{
rxBufferIndex = 0;
rxBufferLength = 0;

txBufferIndex = 0;
txBufferLength = 0;

twi_init();
twi_attachSlaveTxEvent(onRequestService); // default callback must exist
twi_attachSlaveRxEvent(onReceiveService); // default callback must exist
}

void TwoWire::begin(uint8_t address)
{
begin();
twi_setAddress(address);
}

void TwoWire::begin(int address)
{
begin((uint8_t)address);
}

void TwoWire::end(void)
{
twi_disable();
}

void TwoWire::setClock(uint32_t clock)
{
twi_setFrequency(clock);
}

uint8_t TwoWire::requestFrom(uint8_t address, uint8_t quantity, uint32_t iaddress, uint8_t isize, uint8_t sendStop)
{
if (isize > 0) {
// send internal address; this mode allows sending a repeated start to access
// some devices’ internal registers. This function is executed by the hardware
// TWI module on other processors (for example Due’s TWI_IADR and TWI_MMR registers)

beginTransmission(address);

// the maximum size of internal address is 3 bytes
if (isize > 3){
isize = 3;
}

// write internal register address - most significant byte first
while (isize-- > 0)
write((uint8_t)(iaddress >> (isize*8)));
endTransmission(false);
}

// clamp to buffer length
if(quantity > BUFFER_LENGTH){
quantity = BUFFER_LENGTH;
}
// perform blocking read into buffer
uint8_t read = twi_readFrom(address, rxBuffer, quantity, sendStop);
// set rx buffer iterator vars
rxBufferIndex = 0;
rxBufferLength = read;

return read;
}

uint8_t TwoWire::requestFrom(uint8_t address, uint8_t quantity, uint8_t sendStop) {
return requestFrom((uint8_t)address, (uint8_t)quantity, (uint32_t)0, (uint8_t)0, (uint8_t)sendStop);
}

uint8_t TwoWire::requestFrom(uint8_t address, uint8_t quantity)
{
return requestFrom((uint8_t)address, (uint8_t)quantity, (uint8_t)true);
}

uint8_t TwoWire::requestFrom(int address, int quantity)
{
return requestFrom((uint8_t)address, (uint8_t)quantity, (uint8_t)true);
}

uint8_t TwoWire::requestFrom(int address, int quantity, int sendStop)
{
return requestFrom((uint8_t)address, (uint8_t)quantity, (uint8_t)sendStop);
}

void TwoWire::beginTransmission(uint8_t address)
{
// indicate that we are transmitting
transmitting = 1;
// set address of targeted slave
txAddress = address;
// reset tx buffer iterator vars
txBufferIndex = 0;
txBufferLength = 0;
}

void TwoWire::beginTransmission(int address)
{
beginTransmission((uint8_t)address);
}

//
// Originally, ‘endTransmission’ was an f(void) function.
// It has been modified to take one parameter indicating
// whether or not a STOP should be performed on the bus.
// Calling endTransmission(false) allows a sketch to
// perform a repeated start.
//
// WARNING: Nothing in the library keeps track of whether
// the bus tenure has been properly ended with a STOP. It
// is very possible to leave the bus in a hung state if
// no call to endTransmission(true) is made. Some I2C
// devices will behave oddly if they do not see a STOP.
//
uint8_t TwoWire::endTransmission(uint8_t sendStop)
{
// transmit buffer (blocking)
uint8_t ret = twi_writeTo(txAddress, txBuffer, txBufferLength, 1, sendStop);
// reset tx buffer iterator vars
txBufferIndex = 0;
txBufferLength = 0;
// indicate that we are done transmitting
transmitting = 0;
return ret;
}

// This provides backwards compatibility with the original
// definition, and expected behaviour, of endTransmission
//
uint8_t TwoWire::endTransmission(void)
{
return endTransmission(true);
}

// must be called in:
// slave tx event callback
// or after beginTransmission(address)
size_t TwoWire::write(uint8_t data)
{
if(transmitting){
// in master transmitter mode
// don’t bother if buffer is full
if(txBufferLength >= BUFFER_LENGTH){
setWriteError();
return 0;
}
// put byte in tx buffer
txBuffer[txBufferIndex] = data;
++txBufferIndex;
// update amount in buffer
txBufferLength = txBufferIndex;
}else{
// in slave send mode
// reply to master
twi_transmit(&data, 1);
}
return 1;
}

// must be called in:
// slave tx event callback
// or after beginTransmission(address)
size_t TwoWire::write(const uint8_t *data, size_t quantity)
{
if(transmitting){
// in master transmitter mode
for(size_t i = 0; i < quantity; ++i){
write(data[i]);
}
}else{
// in slave send mode
// reply to master
twi_transmit(data, quantity);
}
return quantity;
}

// must be called in:
// slave rx event callback
// or after requestFrom(address, numBytes)
int TwoWire::available(void)
{
return rxBufferLength - rxBufferIndex;
}

// must be called in:
// slave rx event callback
// or after requestFrom(address, numBytes)
int TwoWire::read(void)
{
int value = -1;

// get each successive byte on each call
if(rxBufferIndex < rxBufferLength){
value = rxBuffer[rxBufferIndex];
++rxBufferIndex;
}

return value;
}

// must be called in:
// slave rx event callback
// or after requestFrom(address, numBytes)
int TwoWire::peek(void)
{
int value = -1;

if(rxBufferIndex < rxBufferLength){
value = rxBuffer[rxBufferIndex];
}

return value;
}

void TwoWire::flush(void)
{
// XXX: to be implemented.
}

// behind the scenes function that is called when data is received
void TwoWire::onReceiveService(uint8_t* inBytes, int numBytes)
{
// don’t bother if user hasn’t registered a callback
if(!user_onReceive){
return;
}
// don’t bother if rx buffer is in use by a master requestFrom() op
// i know this drops data, but it allows for slight stupidity
// meaning, they may not have read all the master requestFrom() data yet
if(rxBufferIndex < rxBufferLength){
return;
}
// copy twi rx buffer into local read buffer
// this enables new reads to happen in parallel
for(uint8_t i = 0; i < numBytes; ++i){
rxBuffer[i] = inBytes[i];
}
// set rx iterator vars
rxBufferIndex = 0;
rxBufferLength = numBytes;
// alert user program
user_onReceive(numBytes);
}

// behind the scenes function that is called when data is requested
void TwoWire::onRequestService(void)
{
// don’t bother if user hasn’t registered a callback
if(!user_onRequest){
return;
}
// reset tx buffer iterator vars
// !!! this will kill any pending pre-master sendTo() activity
txBufferIndex = 0;
txBufferLength = 0;
// alert user program
user_onRequest();
}

// sets function called on slave write
void TwoWire::onReceive( void (*function)(int) )
{
user_onReceive = function;
}

// sets function called on slave read
void TwoWire::onRequest( void (*function)(void) )
{
user_onRequest = function;
}

// Preinstantiate Objects //////////////////////////////////////////////////////

TwoWire Wire = TwoWire();

Guru Meditation Error: Core 0 panic’ed (IllegalInstruction). Exception was unhandled.
Memory dump at 0x400e805c: 3df03d00 3db03df0 0f0122f0
Core 0 register dump:
PC : 0x400e8061 PS : 0x00060b33 A0 : 0x800e807e A1 : 0x3ffbc080
A2 : 0x00000001 A3 : 0x00000001 A4 : 0x800891e8 A5 : 0x3ffb5100
A6 : 0x00000000 A7 : 0x3ffbbda0 A8 : 0x800e8046 A9 : 0x3ffbc060
A10 : 0x3ffbd378 A11 : 0x3ffbc08f A12 : 0x00000001 A13 : 0x00000001
A14 : 0x00060b20 A15 : 0x00000000 SAR : 0x00000000 EXCCAUSE: 0x00000000
EXCVADDR: 0x00000000 LBEG : 0x00000000 LEND : 0x00000000 LCOUNT : 0x00000000

On the LCD display it just look like random characters changing on
the screen every second or so.

It is a bit difficult to understand what the problem is. I see that it crashes, and also you say that the communication is not reliable, you see random characters on the screen.

It may sound boring, but have you seen all the tips in FreeRTOS - Open Source RTOS Kernel for small embedded systems ?
It is really important to enable configASSERT() while developing. Also assign enough stack and monitor the usage of heap.

Maybe, when you use FreeRTOS in stead of a simple loop, there is some access to RAM that is not allowed? Maybe a stack overflow, maybe writing outside allocated memory?

You have shown the library, but I don’t see the task that calls the library. How does it work?

Hi !

I have an hint for you : I2C is not multi-thread safe : you should use a mutex to allow your tasks to communicate with I2C devices :wink:

1 Like

I think this is overlooked quite often. Forgetting that the software can only interface with the data coming from and going to the hardware.

The random characters on the display to me sounds exactly like this is the problem: multiple tasks are accessing a hardware resource “at the same time” (or rather in their respective time slices) and as a consequence the buffers are getting filled with invalid data.

One possible solution could be to have a task monitoring a queue which contains information about what to display etc., anything wanting to write to the display should then place an item onto the queue.

This would ensure that your hardware resource is only accessed by one task.