roboterclubaachen / xpcc

DEPRECATED, use our successor library https://modm.io instead
Other
173 stars 39 forks source link

hd44780 not working with STM32F030C8T6 #323

Closed acristoffers closed 6 years ago

acristoffers commented 6 years ago

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;
}
salkinium commented 6 years ago

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
salkinium commented 6 years ago

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.

acristoffers commented 6 years ago

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

acristoffers commented 6 years ago

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

acristoffers commented 6 years ago

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.

acristoffers commented 6 years ago

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.

acristoffers commented 6 years ago

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.

salkinium commented 6 years ago

Good catch! Do you want to create a PR?

acristoffers commented 6 years ago

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.

salkinium commented 6 years ago

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.

acristoffers commented 6 years ago

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.

salkinium commented 6 years ago

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.

salkinium commented 6 years ago

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

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

acristoffers commented 6 years ago

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.

salkinium commented 6 years ago

~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?

acristoffers commented 6 years ago

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).