You can do both. First use a CA such as Let's Encrypt in a "normal" TLS setup. Then configure your clients to pin the public key part of the certificate such that the certificate may be regenerated as per LE's policy (expires after 90 days) without changing the public key itself.
There are various methods to pinning that work universally (when your TLS provider permits your control) and some are protocol-specific such as HPKP.
Firefox and Chrome disable pin validation for pinned hosts whose validated certificate chain terminates at a user-defined trust anchor (rather than a built-in trust anchor). This means that for users who imported custom root certificates all pinning violations are ignored.
As mentioned in the MDN article for HPKP (linked above), HTTP level pinning is ignored if you're using self signed certificates. Worse, if you're using some library such as libcurl
that doesn't persist any state, the pinning request will be forgotten.
Ideally your TLS provider will allow you to enforce your custom public key pinning before sensitive information may be sent to an attacker. You should verify your client-side pinning is working as expected by using a test server with a valid certificate signed by LE but has a different public key as to break the pin.
Why do we still need a CA at all? A CA remains useful because they can revoke your certificate on your request if your server is compromised; which is faster than a mobile package manager will accept your updated client. Additionally, if your client's pinning code ever breaks (maybe your upstream library changed), you'll still have at least the security of the traditional unpinned TLS workflow.