roboterclubaachen/xpcc

hd44780 not working with STM32F030C8T6

Closed this issue · 16 comments

Hi. I tried to use the Hd44780 class with a 16x2 display, single driver, 4 bit databus.
The display is connected as such:

using E  = GpioC13;
using Rw = GpioC14;
using Rs = GpioC15;
using D4 = GpioB9;
using D5 = GpioB8;
using D6 = GpioB7;
using D7 = GpioB6;

To use the class, I created a bus starting at GpioB6, with width=4 and reversed. The Display library commands are being mostly wiped out by the function GpioPortBase::write(uint16_t data).

While debugging, when I get to this function I have the right value in the data variable. However,
after the line

data = xpcc::bitReverse(uint16_t(data << StartPinReversed));

the data is not on the right place, so it gets wiped out by the mask (data & portMask).

The function xpcc::Hd44780Base<DATA, RW, RS, E>::initialize(LineMode lineMode) in the file hd44780_base_impl.hpp should write 0011 in the bus (B6, B7, B8, B9) when it calls Bus<DATA, E, DATA::width>::writeHighNibble(Set8BitBus);. But when GPIOB->ODR = (GPIOB->ODR & ~portMask) | (data & portMask); gets called, the output on the bus is 0000;

How I do the setup:

#include <xpcc/architecture.hpp>
#include <xpcc/driver/display.hpp>

using namespace xpcc::stm32;

namespace lcd
{
    using E    = GpioC13;
    using Rw   = GpioC14;
    using Rs   = GpioC15;
    using Bl   = GpioB5;
    using Data = GpioPort<GpioB6, 4, xpcc::GpioPort::DataOrder::Reversed>;
}

using LCD = xpcc::Hd44780<lcd::Data, lcd::Rw, lcd::Rs, lcd::E>;

int main()
{
    lcd::Bl::setOutput(true); # BackLight

    auto display = new LCD(16, 2);

    xpcc::delayMilliseconds(100);
    display->initialize();
    xpcc::delayMilliseconds(100);

    display->setCursor(0, 0);

    *display << "Hello";
    display->flush();
 
    while(true){}

    return 0;
}

Hm, I've stepped through

GpioPort<GpioB6, 4, xpcc::GpioPort::DataOrder::Reversed>::write(0b0011)

on a STM32F072 but I can't find the problem:

  1. B6-B9 is on the symmetry axis of a 16bit port, so StartPinReversed = StartPin = 6(correct).
  2. The portMask is 0x3c0 = 0b0000'0011'1100'0000 (correct).
  3. xpcc::bitReverse(0x0c0 = 0b0000'0000'1100'0000) returns 0x300 = 0b0000'0011'0000'0000 (correct).
  4. GPIOB->ODR is read as 0 (for me), and written back as 0x300 (correct).

Can you try out the SoftwareGpioPort, so make sure your hardware setup etc is correct?

using Data = xpcc::SoftwareGpioPort<GpioB6, GpioB7, GpioB8, GpioB9>; // bit3, bit2, bit1, bit0

If in your debugger you can read p/x GPIOB-ODR and confirm that the correct bits are actually set high after a Data::write(), but they still don't show up on the physical bus, the lines may be shorted to ground for some reason. In that case SoftwareGpioPort won't help either.

I'll try the SoftwareGpioPort. The difference between ours tests is on line 3, mine returns 0x30, not 0x300.

Oh, and there are no physical problems. I can drive it by driving the lines myself using set() and reset().

If I use SoftwareGpioPort and tell it to write 0x30 it will write 0, because it will use the lower nibble, not the high one.

data & (1 << (width-1)) <= width is 4->3->2->1, so the mask is 0b1000, 0b0100, 0b0010, 0b0001.

Shouldn't this function be shifting the high nibble into the low one to get written?

xpcc::Hd44780Base<DATA, RW, RS, E>::Bus<Data, Enable, 4>::writeHighNibble(uint8_t data)
{
	Bus<DATA, E, 8>::write(data);
}

Because it's only calling write on a Bus with 8 bits, but

xpcc::Hd44780Base<DATA, RW, RS, E>::Bus<Data, Enable, 8>::write(uint8_t data)
{
	DATA::setOutput();
	DATA::write(data);

	E::set();
	xpcc::delayMicroseconds(1);
	E::reset();
}

this function is just writing using DATA, wich is 4 bits and will take only the lower bits.

Changing

xpcc::Hd44780Base<DATA, RW, RS, E>::Bus<Data, Enable, 4>::writeHighNibble(uint8_t data)
{
	Bus<DATA, E, 8>::write(data);
}

to

xpcc::Hd44780Base<DATA, RW, RS, E>::Bus<Data, Enable, 4>::writeHighNibble(uint8_t data)
{
	Bus<DATA, E, 8>::write(data >> 4);
}

makes it work with SoftwareGpioPort.

Good catch! Do you want to create a PR?

I'll investigate a bit more. My display is not going into 2 lines mode and it still doesn't seem to work with GpioPort. I'll see if I can find what's wrong with that.

Ok, for testing reference: I tested this driver only with a 40x4 dual display, using the xpcc::Hd44780Dual class, both with 8-bit and 4-bit busses.
However, in this example @strongly-typed tested a 20x4 display over an I2C expander on STM32F4.
I think I remember him complaining about some bugs too, but I don't remember if he solved them in the end.

Something is happening: if I use the Data type directly, it does not update the µC output, even through GPIOB->ODR is set correctly. If before using I do

GpioB6::setOutput();
GpioB7::setOutput();
GpioB8::setOutput();
GpioB9::setOutput();

then it works as expected. However, it seems to me that both GpioPort::setOutput() and GpioB*::setOutput() do the same thing.

Ah, there is a bug here: The GPIOx->MODER register has 2bit wide entries per GPIO, but the portMask2 has the right width, but the wrong offset.

This

static constexpr uint32_t portMask2 = portMask | (portMask << Width);

ought to be

static constexpr uint32_t portMask2 = (portMask | (uint32_t(portMask) << Width)) << StartPin;

In your example the portMask2 is 0x3fc0, which starts at bit 6 with a width of 8 bits. This then configures B3, B4, B5, B6 as output, which is incorrect. It is supposed to be 0xff000.

Who the heck writes such terrible code? *git blame* Oh it was me…

We need better hardware testing, we need hwut, (paging @strongly-typed).

Made a pull request (#326) for the byte shiffting problem. Still can't figure out why it doesn't go to 2 lines mode by itself. If I send the command after initialize() it works.

Hm, are you waiting 50ms before calling initialize()? The display executes its reset instructions during this time (datasheet says 10ms of busy time). (meh, but only after a power reset, likely not the case here).

Do the above changes fix your GpioPort issues?

Yes, that change fixes it. And I wait 100ms. Duplicating the command works, even if the duplication is inside the initialize function (just calling twice in row).